STM32 外设说明

STM32 提供了一系列外设(Perphirel)用于进行内外部交互和通信。本文对各外设的作用原理、系统架构等进行描述,不包含针对某种工具的使用说明。

GPIO & 硬件

  • GPIO(General Purpose Input Output),通用输入输出口;
  • 其可以被配置为8中输入输出模式;
  • 引脚电平为0~3.3V,部分引脚可以容忍5V的电压;
  • 输出模式可以控制端口高低电平,用以驱动开关、模拟通信协议输出时序等;
  • 输入模式可以读取端口高低电平电压,用以读取开关量、ADC 采集、模拟通信协议接收数据等。

GPIO 基本结构

alt text

可以看到,其多个 GPIO 口被挂载在驱动器上,驱动器接受信号后修改寄存器。寄存器的修改值被挂载在 APB2 总线上进行传输。对于每一组 GPIO(GPIOA、GPIOB 等),都有一组这样的结构,在 APB2 总线上体现为一个节点。

APB2(Advanced Peripheral Bus 2)为 ARM AMBA(Advanced Microcontroller Bus Architecture)总线系列中外设总线的一种。其在 STM32 中被用于链接对实时性要求高、传输速率快的高速外设。其时钟频率较高,通常可以达到的芯片的最高频率。

具体细节可以参见官方参考手册的 GPIO 框图:

alt text

可以看出,其在右侧 I/O 引脚部分将其通过保护二极管连接到 VDDV_{DD}VSSV_{SS},用于防止静电放电(ESD)或者外部电压对芯片内部电路造成破坏。当输出电压高于 VDD+0.7VV_{DD}+0.7V 或小于 VSS0.7VV_{SS}-0.7V 时,相应的二极管导通,将电流释放到电源或地。

从该部分往左看,上方为输入驱动器。其通过联通 VDDV_{DD}VSSV_{SS} 的上/下拉电阻用以确保引脚在悬空时的电平状态;此后,其经过了一个 TTL 肖特基触发器(施密特触发器),勇气整形输入信号。

施密特触发器拥有两个不同的阈值电压:当输入该触发器的电压上升到正向阈值电压(VT+V_{T+})时,输出才会从低电平变为高电平。当输入电压下降到负向阈值电压(VTV_{T-})时,输出才会从高电平变为低电平。这可以有效的去除高频毛刺噪声。该部分在模拟电子技术中有所描述。

此后,信号进入输入数据寄存器 IDR 存储,即将该寄存器置位为施密特触发器的输出(高低电平,1/0)。

该寄存器位并非与施密特触发器电压直接相连。STM32 的硬件设计中有一组触发器。在标准输入输出模式下,这个触发器连接着施密特触发器的输出端。在 APB2 外设时钟开启时,触发器会在每个时钟周期的上升沿对施密特触发器的输出进行采样。如果采样结果发生改变,触发器会将其更新到 IDR 寄存器对应的比特位内部的存储元件。

该触发器就是 D 触发器。寄存器的每一位就存储在不同的 D 触发器上,即 D 触发器就是最小存储单元。其会在触发边沿到来时,将输入端的值存入,并且这个值和当前存储的值无关。D 连接到施密特触发器的输出信号,CLK 连接到内部总线时钟,Q 连接到内部数据总线的速写缓冲门。该部分在数字电子技术中有所描述。下图为一个 D 触发器的封装和 CMOS 逻辑门构成的 D 触发器内部构造。

alt text

alt text

STM32的每一组 GPIO 一共有 16 个引脚(P0-P15),对应的 IDR 寄存器就是一个 16 位的寄存器组合。在硬件物理布局上,就有 16 个 D 触发器被整齐的排列在一起。

至此,我们的 GPIO 高低电平被记录在寄存器中。

此外,在经过施密特触发器后,其还分流输入到复用功能输入,其会输入到其他外设(AFIO)中进行进一步操作。在经过施密特触发器之前,其会分流到模拟输入中。由于 ADC 需要精确的电压输入,所以不能经过施密特触发器。

下方是输出驱动器。起负责将内部信号通过引脚输出到外部电路。首先,总线写入位寄存器,该寄存器被读出到输出数据寄存器。该寄存器的读出和复用功能输出通过多路选择器(MUX)输出到输出控制。输出控制通过寄存器配置决定。在推挽输出模式下,P-MOS 和 N-MOS 交替导通。此时,高低电平的驱动能力均很强。在开漏输出模式下,P-MOS 不工作,仅由 N-MOS 控制。输出低电平时有效(接地),输出高电平需要外接上拉电阻。此后,该信号被输出到 I/O 引脚。

GPIO 模式

通过配置 GPIO 端口配置寄存器,端口可以配置为以下模式:

  • 浮空输入:可以读取引脚电平,若引脚悬空,则电平不确定
  • 上拉输入:可读取引脚电平,内部连接上拉电阻,悬空时默认高电平
  • 下拉输入:可读取引脚电平,内部连接下拉电阻,悬空时默认低电平
  • 模拟输入:GPIO 无效,引脚接入 ADC 外设
  • 开漏输出:可输出引脚电平,高电平为高阻态,低电平接VSS
  • 推挽输出:可输出引脚电平,高电平接VDD,低电平接VSS
  • 复用开漏输出:由片上外设控制,高电平为高阻态,低电平接VSS
  • 复用推挽输出:由片上外设控制,高电平接VDD,低电平接VSS

在浮空输入下,右侧 I/O 引脚的 VDDV_{DD}VSSV_{SS} 均断开,所以浮空时电平不确定;在上拉模式下,只有 VSSV_{SS} 断开,所以浮空时引脚只连接到 VDDV_{DD},即为高电平。下拉同理。

EXTI 外部中断

中断是在主程序运行过程中,出现了特定的中断触发条件(中断源),使得 CPU 暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行的行为。

当有多个中断源同时申请中断时,CPU 会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源,即中断优先级。

当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU 会再次暂停当前的中断程序,转而去处理新的中断程序,处理完后依次进行返回。

下图为中断的逻辑框图:

alt text

AFIO 为选择中断引脚,其会收集 GPIOx 的中断源,并并在其中选择由哪一个/哪些中断进入到 EXTI 中。对于任意组的 GPIO,只有一路 Pin 可以被接入到 EXTI 中,如下图所示:

alt text

之后,中断信号进入 EXTI。下图为 EXTI 的手册框图:

alt text

其中,输入线即为上一步骤输入的 GPIO 信号。为了方便说明,我们将其编号:

alt text

编号 1 是输入线,EXTI 控制器有 19-20 个中断/事件输入线,这些输入线可以通过寄存器设置为任意一个 GPIO,也可以是一些外设的事件。

编号 2 是一个边沿检测电路,它会根据上升沿触发选择寄存器(EXTI_RTSR) 和下降沿触发选择寄存器 (EXTI_FTSR) 对应位的设置来控制信号触发。边沿检测电路以输入线作为信号输入端,如果检测到有边沿跳变就输出有效信号 1 给编号 3 电路,否则输出无效信号 0。而 EXTI_RTSR 和 EXTI_FTSR 两个寄存器可以控制需要检测哪些类型的电平跳变过程,可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发。

编号 3 电路实际就是一个或门电路,它的一个输入来自编号 2 电路,另外一个输入来自软件中断事件寄存器 (EXTI_SWIER)。EXTI_SWIER 允许我们通过程序控制就可以启动中断/事件线, 这在某些地方非常有用。

编号 4 电路是一个与门电路,它的一个输入是编号 3 电路,另外一个输入来自中断屏蔽寄存器 (EXTI_IMR)。与门电路要求输入都为 1 才输出 1,导致的结果是如果 EXTI_IMR 设置为 0 时,那不管编号3电路的输出信号是 1 还是 0,最终编号 4 电路输出的信号都为 0;如果 EXTI_IMR 设置为 1 时,最终编号 4 电路输出的信号才由编号 3 电路的输出信号决定,这样我们可以简单的控制 EXTI_IMR 来实现是否产生中断的目的。编号 4 电路输出的信号会被保存到挂起寄存器 (EXTI_PR) 内,如果确定编号 4 电路输出为1就会把 EXTI_PR 对应位置 1。

编号 5 是将 EXTI_PR 寄存器内容输出到 NVIC 内,从而实现系统中断事件控制。

TIM 定时器

  • 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断;
  • 不仅具备基本的定时器中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能; STM32的定时器支持级联的模式:一个定时器的输出当做另一个定时器的输入最大定时时间就是59.65s * 65536 * 65536。

定时器基本结构

STM32 具有多种定时器:

alt text

下图为通用定时器的手册框图:

alt text

框图中上部为时基单元,为整个定时器的时间基准,负责产生计数和产生时钟/事件。其时钟源选择可以通过内部时钟 CK_INT,即 RCC 主时钟产生,也可以通过外部触发输入 ETR,通过极性选择、边缘检测、分频后进入,也可以通过内部触发 ITRx 输入(用于定时器级联)。这些输入作为 TRGI(触发输入)和内部时钟一样进入触发控制器,然后通过 TRGO(触发输出)向其他外设输出。同时,触发控制器含有编码器接口和从模式控制器,从模式控制器的信号(复位、使能、向上/向下、计数)发送到预分频器(PSC)中。其将所选时钟源进行 1~65536 之间的分频,生成计数器时钟 CK_CNT。此后,由 CNT 计数器进行向上/向下/中心对齐的计数。当计数器达到自动重装寄存器存放的重装值时,起会产生更新时间 U,用于清除寄存器、触发中断、DMA 请求等。

CNT 计数器的值会发送到捕获/比较寄存器。在输出比较时,其作为比较寄存器使用,存放设定的比较值。当计数器的值到达设定的比较值时,其会生成参考信号 OCxREF,经过输出控制后输出到定时器通道引脚 TIMx_CHx。

框图左下侧为输入捕获用的外部输入。TIMx_CHx 为输入信号,在外设中进入输入滤波器和边沿检测器,可以作为时基单元的输入。之后,其生成信号 TIxFP1,和相邻通道的 TIxFP2 一起进入选择 MUX,也可以作为编码器接口和 TRC。通过 MUX 选择后,若捕获事件发生,系统会将 CNT 计数器的值作为捕获寄存器 CCR 的值。

TIxFPx 为 Timer Input x, Filter, Polarity x 的缩写。即表示:从通道 x 引入,经过输入滤波器滤波并经过极性选择器选择后的通道 x 信号。

在数据被锁存到 CCR 中之后,定时器会向外触发一系列标志/请求,用于提供给 CPU 或者外设:CCxI 用于表示捕获成功,中断请求、DMA 请求等。

定时器计算公式

计数器的时钟频率 fCK_CNTf_{CK\_CNT} 计算公式为:

fCK_CNT=fCK_PSCPSC+1f_{CK\_CNT} = \frac{f_{CK\_PSC}}{PSC + 1}

fCK_PSCf_{CK\_PSC}:定时器的输入时钟频率(通常等于内部时钟 fCK_INTf_{CK\_INT},在某些高级定时器下频率可能会倍频)。

PSCPSC:预分频器寄存器(Prescaler)的值,范围为 0655350 \sim 65535

加 1 的原因:由于寄存器值为 00 时代表 1 分频,因此分频系数为 PSC+1PSC + 1

当计数器向上或向下溢出时产生更新中断,单次中断的时间 ToutT_{out} 计算公式为:

Tout=(ARR+1)×(PSC+1)fCK_INTT_{out} = \frac{(ARR + 1) \times (PSC + 1)}{f_{CK\_INT}}

ARRARR:自动重载寄存器(Auto-reload register)的值,范围为 0655350 \sim 65535(基本/通用定时器)或 042949672950 \sim 4294967295(32位定时器)。

在生成 PWM 波形时,输出的信号频率 fPWMf_{PWM} 和占空比 DD 计算公式为:

fPWM=fCK_PSC(ARR+1)×(PSC+1)f_{PWM} = \frac{f_{CK\_PSC}}{(ARR + 1) \times (PSC + 1)}

D=CCRARR+1D = \frac{CCR}{ARR + 1}

CCRCCR:捕获/比较寄存器(Capture/Compare register)的值,决定脉冲的高电平(或低电平)持续时间。

当使用输入捕获测量外部周期性信号时,若定时器计数频率为 fCK_CNTf_{CK\_CNT},两次捕获的时间差(计数值为 NN)对应的频率 finf_{in} 计算公式为:

fin=fCK_CNTNf_{in} = \frac{f_{CK\_CNT}}{N}

当测量高电平脉宽时,若记录上升沿的计数值为 C1C_1,下降沿的计数值为 C2C_2,脉宽时间 TwidthT_{width} 计算公式为:

Twidth=C2C1fCK_CNTT_{width} = \frac{C_2 - C_1}{f_{CK\_CNT}}

在使用正交编码器模式时,电机的实际转速与计数值和采样周期相关。转速计算公式为:

n=CNTdiff4×P×Tsamplen = \frac{CNT_{diff}}{4 \times P \times T_{sample}}

CNTdiffCNT_{diff}:在一个采样周期内 CNT 寄存器变化的计数值。

PP:编码器单圈的物理脉冲数(PPR)。

44:正交解码的 4 倍频系数(A/B相的上升沿和下降沿均计数)。

TsampleT_{sample}:软件设定的采样周期(单位为秒)。

定时器模式

输出比较

输出比较(Output Compare,OC),可以通过比较 CNT 和 CCR 寄存器值的关系,来对输出电平进行置 1/0 或翻转的操作,用于输出一定频率和占空比的 PWM 波形。每个定时器拥有四个输出比较通道,其中高级定时器的前三个通道具有死区生成和互补输出的功能。

死区是在两个互补的 PWM 信号之间人为插入的一段都为关闭的时间,用以防止某两个功率器件同时导通。比如对于某种半桥驱动,比如 MOSFET 和 IGBT,上管 PWM 为高电平导通,下管 PWM 为低电平导通,如果直接互补切换则会出现上管尚没有完全断开时下管已经打开,会导致上下管直通,从而短路。死区的波形如图所示。

alt text

输入捕获

输入捕获(Input Capture,IC),是当通道的输入引脚出现电瓶跳变时,当前 CNT 的值直接写入到 CCR 中,可用于测量 PWM 波形的频率、占空比、脉冲间隔、电平持续时间等。每个定时器有四个输入捕获通道,可以配置为 PWMI 模式用于同时测量频率、占空比。其还可以通过设置为主从触发模式,实现硬件自动测量。

频率的测量有两类,一种为测频法。其在闸门时间 TT 内,对上升沿计次,得到 NN,可得到频率 FTF_T

FT=NTF_T = \frac{N}{T}

alt text

通常设置 TT 为 1s,即在 1s 中来了几个脉冲则频率是多少。(频率的定义:在 1s 内出现的重复周期数)。

另一种为测周法。其在两个上升沿内用标准频率计次,得到 NN,则频率:

FX=fcNF_X=\frac{f_c}{N}

alt text

使用一个已知频率 fcf_c 的计次时钟,从一个上升沿开始计数一直到下一个上升沿,则计一个数的时间为 1fc\frac{1}{f_c},计 NN 个数的时间为 Nfc\frac{N}{f_c}Nfc\frac{N}{f_c} 为周期,则其倒数就是频率。

测周法只测量一个周期,就能出一次结果,出结果的速度取决于待测信号的频率,一般来说测周法结果更新更快,但是由于他只测量一个周期,所以结果值会受噪声的影响,波动比较大。

测频法适合测高频信号,测周法适合测量低频信号。两者误差相等的点为:

fm=fcTf_m = \sqrt{\frac{f_c}{T}}

当待测频率小于中界频率,测周法合适;当待测频率大于中界频率,测频法合适。

同时,我们可以采用第一个捕获通道使用上升沿触发,用来捕获周期,第二个通道使用下降沿触发,用来捕获占空比,两个通道同时对一个引脚进行捕获,就可以同时测量频率和占空比,这就是 PWMI 模式。

主从触发模式

主模式可以将定时器内部的信号映射到 TRGO 引脚,用于触发别的外设;从模式可以接收其他外设或者自身外设的信号,用于控制自身定时器的运行,也就是被别的信号控制。

例如,可以让 TI1FP1 信号触发 CNT 清零,触发源选择 TI1FP1。从模式可以选择执行 Reset 操作,这样 TI1FP1 就可以自动触发从模式,从模式清零 CNT,实现硬件自动测量。

alt text

编码器

编码器接口可以接收正交信号,根据正交信号的顺序产生脉冲,控制 CNT 自增自减,从而 指示编码器位置、方向、角度等。

硬件编码器的 AB 相分别接两个定时器通道,编码器接口自动控制时基单元中的CNT计数器,进行自增或者自减。例如CNT初始值为0,编码器右转CNT++,右转产生一个脉冲,CNT++,左转CNT–,编码器接口同时控制CNT的计数时钟和计数方向,CNT的值就表示了编码器的位置,每隔一段时间取一次CNT的值再把CNT清零,每次取出来的值就带标 了编码器的速度,编码器的测速实际上就是测频法测正交脉冲的频率,CNT计次,每隔一段时间取一次计次,也可以用外部中断来接编码器。

alt text

当编码器的旋转轴转起来时,A相和B相就会输出方波信号,转的越快,方波的频率越高,方波的频率就代表了速度,取出任意一相的信号来测量频率,就能知道旋转速度,只有一相的信号无确定旋转方向。

其基本结构如下:

alt text

ADC 模拟-数字转换器

可以参考 ADC 外设

ADC(Analog-to-Digital Converter)即模数转换器,负责将连续的模拟电压信号(如传感器输出)转换为数字序列,供微控制器(MCU)处理。

STM32F411 最高支持 12 位 ADC,即支持将电压信号转换为占 12 位的数字,这代表了他的精度(分辨率)。12 位意味着其分辨率可以到 212=40962^{12}=4096。也就是说,对于 0 ~ 3.3V 的电压,芯片可以将其转换为 0 ~ 4095 的数字。其转换时间最快为 1us,当 ADC 时钟为 14HMz 时。

ADC 基本结构

alt text

这是 STM32F4xx 系列 User Manual 中提供的单路 ADC 的结构图。其揭示了芯片内部有关 ADC 的硬件结构设计。可以看到,对于一个 ADC,其具有多个通道(CH,Channel),其经过模块 GPIO ports 传入单片机中,经过模拟多路开关 Analog mux 进入 ADC 核心。模拟多路开关决定了是哪些通道传入到核心。其中,最多 4 个通道传输到注入通道 Injected channels,最多 16 个传输到规则通道 Regular channels。这两个通道通过相应的数据寄存器传输到地址/数据总线 Address/data bus 上,从而进行控制和数据传输。其中,以规则通道为例,其触发(开启)收到定时器组(启动触发器 Start trigger (regular group))控制,定时器信号是否发送、边沿设定通过寄存器 EXTEN 控制,定时器 TIMx 通过寄存器 EXTSEL 选线。同时,其还支持选择外部中断 EXTI_11 作为触发源。

Regular channels 相当于规则通道组,为转换主任务,Injected channels 相当于中断任务,当规则组转换时注入组被触发,它会打断规则组优先进行转换,转换完后再恢复规则组。

通道的数据寄存器除了直接抵达总线外,还会触发中断。其通过一组标志位识别转换结束 EOC、注入转换结束 JEOC,以及一组使能位控制中断是否触发:EOCIE/JEOCIE,该组中断被配置触发后会发送信号至 NVIC 的 ADC 中断。

此外,可以注意到其时钟信号来自 ADCCLK,这是总线 APB2 时钟 PCLK2 分频(Prescale)的结果。同时,模拟多路开关有其他几个输入:Temp.sensor 是来自单片机内核的温度传感器信号,VREFINTV_{REFINT} 是内部参考电压,用于校准,VBATV_{BAT} 为引脚电压。

下面有一个更为简单的图用于描述上述结构:

alt text

根据上面的描述,ADC 触发读取(扫描/转换)有以下方式:

  • 软件触发,程序中调用函数启动转换
  • 硬件触发,通过定时器/外部中断引脚触发

ADC 转换模式

根据转换次数和转换方式分类,其分为单次转换和连续转换。单次转换仅触发一次,转换一个通道或一组通道,完成后停止。连续转换触发一次后循环转换,无需重复触发。

根据通道数量,可以分为非扫描模式和扫描模式。非扫描模式每次仅转换单个通道,扫描模式按序列依次转换多个通道。扫描模式需要利用 DMA 等方式转运数据。

以上方式可以随意排列组合:

  • 单次转换,非扫描模式

alt text

  • 连续转换,非扫描模式

alt text

  • 单次转换,扫描模式

alt text

  • 连续转换,扫描模式

alt text

ADC 数据对齐

其提供了两种数据对齐方式:左/右对齐。

右对齐指 12 位数据存放在寄存器低 12 位(默认方式),直接读取即可得到原始值;左对齐指 12 位数据存放在寄存器高 12 位,低 4 位补 0,适用于需要降低分辨率的场景(如转换为 8 位数据)。

逐次逼近型 ADC 工作原理

STM32 微控制器中内置的ADC使用SAR(逐次逼近)原则,分多步执行转换。转换步骤数等 于ADC转换器中的位数。每个步骤均由ADC时钟驱动。每个ADC时钟从结果到输出产生一 位。ADC的内部设计基于切换电容技术。

alt text

具体细节在此不再赘述,可以参考:

SAR ADC 逐次逼近型ADC Successive Approximation Register ADC

DMA 数据转运

DMA(Direct Memory Access),直接存储器读取,可以用来协助 CPU 完成数据转运的工作,提供外设到存储器、存储器之间的高速数据传输。

芯片内部可以有 12 个独立可配置的通道,DMA1 有七个通道,DMA2 有五个通道。每个通道都支持软件和特定的硬件触发。存储器之间的一般用软件触发,外设到存储器的一般用硬件触发。

计算机系统分为五个部分:运算器、控制器、存储器、输入设备和输出设备。运算器和控制器一般合在一起叫做 CPU。

存储器分为两大类:ROM 和 RAM。ROM 是只读存储器,是一种非易失性、掉电不丢失的存储器;RAM 是随机存储器,是一种易失性、掉电丢失的存储器。ROM 分为三块,第一块是程序存储器 Flash,即主闪存,用于存储 C 语言编译后的程序代码,也就是下载程序的位置,运行程序一般从主闪存中开始运行。另外两部分是系统存储器和选项字节,这两块存储器的存储介质也是 Flash,即非主闪存Flash。系统存储器是用来存储 BootLoader。BootLoader 程序一般是芯片出厂自动写入的,一般不允许修改,选项字节存的主要是 Flash 的读保护、写保护、看门狗等的配置。RAM 中的运行内存 SRAM 存储我们程序中定义变量、数组、结构体的地方,外设寄存器存储我们初始化各个外设,读写外设的数据。外设寄存器起始也是 SRAM,存储内核外设 NVIC 和 SysTick。

alt text

上图为芯片内部的结构图,其显示了 DMA 请求和总线与各外设之间的结构关系。

左上角是 Cortex-M3 内核,里面包含了 CPU 和内核外设。其余所有东西都可以看成是存储器,Flash 是主闪存,SRAM 是运行内存,各个外设都可以看成是寄存器,也是一种 SRAM 存储器。寄存器是一种特殊的存储器。一方面,CPU 可以对寄存器进行读写,就像读写运行内存一样,另一方面,寄存器的每一位背后,都连接了一根导线,这些导线可以控制外设电路的状态,比如置引脚的高低电平,导通和断开、切换数据寄存器,或者多位结合起来,当做计数器、数据寄存器,寄存器是连接软件和硬件的桥梁,软件读写寄存器,就相当于在控制硬件的执行,使用 DMA 进行数据转运,就相当于从某个地址取内容,再放到另一个地址去。

为了高效有条理地访问存储器,ST 设计了一个总线矩阵。总线矩阵的左端为主动单元,也就是拥有存储器的访问权,右侧为被动单元,它们的存储器只能被左边的主动单元读写。主动单元内核有 DCode 和系统总线,可以访问右边的存储器,其中 DCode 总线是专门访问 Flash 的,系统总线是访问其他外设的。由于 DMA 要转运数据,所以 DMA 也必须要有访问的主动权,主动单元除了内核、CPU 剩下的就是 DMA 总线。DMA1 和 DMA2 都各自有一条总线,下面以太网外设自己也私有一个 DMA 总线。DMA1 有 7 个通道,DMA2 有 5 个通道,各个通道可以分别设置它们转运数据的源地址和目的地址。由于 DMA 只有一条总线,仲裁器可以根据通道的优先级决定哪个通道先用。在总线矩阵里也有一个仲裁器,如果 DMA 和 CPU 都要访问同一个目标,那么 DMA 就会暂停 CPU 的访问,以防止冲突,不过总线仲裁器仍然会保证 CPU 得到一半的总线带宽,使 CPU 也能正常工作。下面的 AHB 从设备就是 DMA 自身的寄存器。DMA作为一个外设,也会有相应的配置寄存器连接在总线右边的AHB总线上。所以 DMA 既是总线矩阵的主动单元,可以读写各种存储器,也是 AHB 总线上的被动单元,DMA 请求就是 DMA 的硬件触发源,比如说 ADC 转换完成、串口接收到数据需要触发 DMA 转运数据的时候,就会通过这条线路,向 DMA 发出硬件触发信号,之后 DMA 就可以执行数据转运的工作了,这就是DMA请求的作用。

AHB 总线,即 Advanced High-performance Bus,高级高性能总线,主要用于高性能、高时钟频率的系统模块(如处理器、DMA 控制器、片上高带宽存储器等)之间的互连,充当整个片上系统(SoC)的高速数据传输主干。

现在就可以解释从 C 语言编译的部分到读取 D 触发器锁存值的硬件逻辑了。

我们在 C 语言中可以对寄存器进行赋值,例如:

GPIOB->BSRR = GPIO_Pin_8;

我们在 C 语言中对寄存器进行操作的本质,是对特定内存地址的读写。所以,上面的赋值可以转化为类似下面的形式:

*(volatile unsigned int *)0x4001080C = 0x00000001;

编译器 arm-none-eabi-gcc 就会将其编译为 ARM Thumb-2 指令集:

LDR     r0, [pc, #12]    ; 加载外设基地址
LDR     r1, [r0, #0x10]  ; 读取当前寄存器状态
ORR     r1, #0x00000100  ; 置位对应引脚
STR     r1, [r0, #0x10]  ; 写入 BSRR 寄存器

此时,对于 Cortex-M 内核,其内部的加载/存储单元(Load/Store Unit, LSU)将这个地址 0x4001080C 放到地址总线上,将数据 0x00000001 放到数据总线上。Cortex-M 引出系统总线把上面的数据和地址引出。

Cortex-M3 采用的 Harvard 架构,即具有独立的指令总线和数据总线。因此数据访问通过系统总线(System Bus)或 D-Code 总线进行,不会阻塞指令预取。

每个引出的总线(包括系统总线、D-Code、I-Code)内部都具有多个总线:地址总线(AB),数据总线(DB),控制总线(CB)。

数据和地址通过系统总线到达总线矩阵。由于 GPIO 挂载在速度较低(相对于 AHB)的 APB2 总线上,所以需要由总线矩阵将高速的 AHB 写入通过桥接器进行时钟同步和降频。此后,该信号通过 APB2 总线广播,然后通过控制总线握手确认通信。

对于每个总线,都有一个仲裁器机构。从存储寄存器的 D 触发器开始,信号在由端口 Q 输出后,会经过一个三态门,由三态门连接到总线。

三态门具有高电平、低电平、高阻态三种状态。高低电平用来传输数据,高阻态表示断开,即断开该节点和总线的连接。下图为三态门连接总线的结构和三态门的内部构成。

alt text

alt text

在 AHB 转化到 APB2 总线时,控制总线有以下控制信号:

  • PCLK:外设时钟
  • PSELx:外设片选,由地址译码器产生
  • PENABLE:使能信号
  • PWRITE:读写控制
  • PREADY:外设就绪信号(外设向总线发送的握手)

译码器是一组将较小位数的二进制代码映射成多个具有特定含义的输出信号的组件。比如三八译码器,就是将三根线用来表示八种状态,即 23=82^3=8,可以用来表示八个不同的地址。

当总线将目标地址输出到地址总线上时,译码器会拉高 PSELx,选中 GPIO 外设。此后,总线拉高 PWRITE,表示写操作。之后,数据总线传输待写入的数据。此时 PENABLE 为低。这样,数据被传输到总线上,即 APB 接口电路的输入缓冲器中。

之后,总线将 PENABLE 信号拉高,标志传输周期开始。GPIO 由于外设速度低于总线,可以通过拉低 PREADY 信号强制使总线等待。当 PREADY 信号为高且 PCLK 时钟信号在上升沿,数据被锁存在 D 触发器中。

回到 DMA 外设。

alt text

DMA 的数据转运可以从外设到存储器,也可以是从存储器到外设,也可以从存储器转运到存储器。外设和存储器两端都有 3 个参数,第一个是起始地址,有外设端的起始地址和存储器端的起始地址,这两个参数决定了数据时从哪里来,到哪里去的。第二个参数是数据宽度,这个参数的作用是指定一次转运要按多大的数据宽度来进行,可以选择字节 Byte、半字节 HalfWord 和字 Word。每字节就是 8 位转运一个 uint8_t,半字节是 16 位uint16_t,字是 32 位 uint32_t。例如 ADC 的数据,ADC 的数据是 uint16_t,所以参数就要选择半字节,依次转运一个 uint16_t。第三个参数是地址是否自增,这个参数的作用是,指定一次转运完成后,下一次转运是不是要把地址移动到下一个位置去,相当于是指针 p++,比如 ADC 扫描模式,用DMA 转运数据,外设地址是 ADC_DR 寄存器,寄存器显然地址是不用自增的,存储器这边地址就需要自增,每转运一个数据后,就往后挪一个位置,要不然下次再转就把上次的覆盖掉了。这就是地址是否自增的作用。

传输存储器是来指定总共转运几次。其是个自减计数器,比如值为 5,那么 DMA 就只能进行 5 次数据转运,转运过程中,每转运一次计数器的值就会减 1,当传输计数器减到 0 之后,DMA 就不会再进行数据转运了,减到 0 之后之前自增的地址也会恢复到起始地址的位置,以方便之后 DMA 新一轮的转运。传输计数器的右边的自动重装器的作用是,传输计数器减到 0 之后,是否要自动恢复到最初的值。比如传输计数器给 5,如果不使用自动重装器,那转运 5 次后,DMA 就结束了,如果使用自动重装器,那转运 5 次,计数器减到 0 后,就会立即重装到初始值 5。自动重装器决定了转运的模式,如果不重装,就是正常的单次模式,如果重装就是循环模式,如果你想转运一个数组,则一般是单次模式,转运一轮就结束了,如果是 ADC 扫描模式+连续转换,那为了配合 ADC,DMA 也需要使用循环模式,这个循环模式和 ADC 的连续模式差不多。

DMA 的触发控制决定 DMA 在什么时机进行转运的,触发源有硬件触发,和软件触发,具体选择由 M2M(Memory to Memory)参数决定。当给 M2M 位 1 时,DMA 就会选择软件触发,这个软件触发不是调用某个函数一次就触发一次,而是,以最快的速度,连续不断地发出 DMA,一直到传输计数器清 0。软件触发和循环模式不能同时用,因为软件触发是想把传输计数器清零,循环模式是清零后自动重装,如果同时用,那 DMA 就停不下了,软件触发一般适用于存储器到存储器的转运,因为存储器到存储器的转运是软件启动不需要时机。当 M2M 位给 0,就是使用硬件触发,硬件触发源可以选择 ADC、串口、定时器等等,使用硬件触发的转运一般是与外设有关的转运,这些转运需要一定的时机,比如ADC转换完成、串口收到数据、定时时间到等等。当硬件达到这些时机时,传一个信号过来,来触发 DMA 进行转运。

DMA1 的请求框图如图所示。

alt text

图中是DMA的7个通道,每个通道都有一个数据选择器,可以选择硬件触发和软件触发。对于左侧的硬件触发源,每个通道的硬件触发源都是不同的,如果选择软件触发源就可以任意选择。各个通道会进入仲裁器区分优先级,然后触发 DMA1 请求。

下图表示了可以被转运的数据宽度、对齐方式和大小端的详细情况。

alt text

USART 串口

通信协议(接口)概述

  • 全双工指通信双方可以同时进行双向通信,一般有两根通信线;
  • 单工指数据只能从一个设备到另一个设备;
  • Tx 和 Rx 为单端信号,他们的高低信号是相对于 GND 的;
  • Tx 和 Rx 要交叉连接;
  • 电平标准是数据 1 和数据 0 的表达方式,是传输线缆中人为规定的电压和数据的对应关系。常用的有三种:
    • TTL 电平,+3.3V 或 +5V 为 1,0V 表示 0;
    • RS232 电平,-3V ~ -15V 表示 1,+3V ~ +15V 表示 0
    • RS485 电平,两线压差 +2V ~ +6V 表示 1,-2V ~ -6V 表示 0(差分信号)

Usart,即 Universal Synchronous Asynchronous Receiver Transmitter,通用同步异步收发器,是一种串行通信接口。他支持异步通信(不需要时钟线,靠起始位同步),也支持同步通信(有单独的时钟线)。

常用的为 Uart,即异步串口。

串口参数和时序

串口有以下概念:

  • 波特率:指串口通信的速率
  • 起始位:标志一个数据真的开始,固定位低电平(空闲状态为高电平)
  • 数据位:数据帧的有效载荷,1 为高电平,0 为低电平,低位先行
  • 校验位:用于数据验证,根据数据位计算得来
  • 停止位:用于数据帧间隔,固定为高电平

alt text

串口中,每一个字节都装载在一个数据帧里面,每个数据帧都由起始位、数据位和停止位组成,数据位有 8 个,代表一个字节的 8 位,还可以在数据位的最后加一个奇偶校验位,这样数据位总共就是 9 位,其中有效载荷时前 8 位,代表 1 个字节,校验位跟在有效载荷后面,占 1 位。

波特率规定了串口通信的速率(串口一般使用异步通信,需要双方约定一个通信速率),例如每隔 1s 发送一位,接收方也要每隔 1s 接收一位。发送和接收必须约定好速率,波特率本义是每秒传输码元的个数,单位是码元/s,或者直接叫波特(Baud),比特率是每秒传输的比特数,单位是 bit/s,或者叫 bps,在二进制调制的情况下,一个码元就是一个 bit,此时波特率就等于比特率,单片机的串口通信,基本都是二进制调制,也就是高电平表示 1,低电平表示 0,一位就是 1bit,规定波特率为 1000bps,表示 1s 要发 1000 位,每一位的时间就是 1ms,发送方每隔 1ms 发送一位,接收方每隔 1ms 接收一位。

起始位标志一个数据帧的开始,固定为低电平(串口的空闲状态是高电平,没有数据传输的时候引脚必须置高电平,作为空闲状态)。所以需要传输的时候先发送一个起始位,起始位必须是低电平,来打破空闲状态的高电平,产生一个下降沿(告诉接受设备,这一帧数据要开始了)。如果没有起始位,当发送 8 个 1 的时候,数据线一直都是高电平,没有任何波动,这样接收方就不知道我是否发送数据,所以必须要有一个固定为低电平的起始位,产生下降沿,来告诉接受设备,为要发送数据了-----起始位固定为 0,产生下降沿,表示传输开始。

同时,在一个字节数据发送完成后,必须要有一个停止位。这个停止位用于数据帧间隔,固定为高电平,同时这个停止位也是为下一个起始位做准备的,如果没有停止位,那当为数据最后一位是 0 的时候,下次再发送新的一帧,就没法产生下降沿了-----停止位固定为 1,把引脚恢复成高电平,方便下一次的下降沿,如果没有数据了,引脚也为高电平,代表空闲状态。

alt text

校验位用于数据验证,是根据数据位计算得来的。串口使用奇偶校验位方法,奇偶校验可以用来判断数据传输是不是出错了,如果数据出错了可以选择丢弃或者要求重传。校验可以选择三种方式,无校验、奇校验和偶校验。无校验就是不需要校验位,波形就是上图左边的,起始位、数据位,停止位一共 3 个部分,奇校验和偶校验的波形就是上图右边的,起始位、数据位、校验位、停止位,总共 4 个部分,如果使用了奇校验,那么包括校验位在内的9位数据会出现奇数个1,如果传输 0000 1111,目前总共 4 个 1,是偶数个,那么校验位就需要再补一个 1,连同校验位就是 0000 1111 1,总共 5 个 1,保证 1 的个数为奇数,如果数据是 0000 1110,此时 3 个 1,是奇数个,那么校验位就补 1 个 0,连同校验位就是 0000 1110 0,总共还是 3 个 1,1 的个数为奇数,发送方,在发送数据后,会补一个校验位,保证 1 的个数为奇数,接收方在接收数据后,会验证数据位和校验位,如果 1 的个数还是奇数,就认为数据没有出错,如果在传输中,因为干扰,有一位由 1 变成 0,或者由 0 变成 1 了,那么整个数据的奇偶特性就会变化,接收方一验证,发现 1 的个数不是奇数,那就认为传输出错,就可以选择丢弃,或者要求重传,这就是奇校验的差错控制方法。如果选择双方约定偶校验,那就是保证 1 的个数是偶数,校验方法也是同理,但是奇偶校验的检出率不是很高,例如,如果有两位数据同时出错,就特性不变,那就校验不出来了,就能校验只能保证一定程度上的数据校验,如果想要更高的检出率可以选择 CRC 校验,STM32 内部也有 CRC 外设。

Usart 基本结构

下面是 Usart 的官方框图:

alt text

通过 Usart 发送和接收的字节数据存在串口的数据寄存器中。数据寄存器分为发送数据寄存器 TDR(Transmit Data Register)和接收数据寄存器(Receive DR)。在程序上表现为一个寄存器 DR,但是硬件上分为两个。TDR 是只写的,RDR 是只读的。当对 DR 进行写操作时,其就流入 TDR,当读取时,就读取 RDR。除此之外,还有两个移位寄存器,用于把字节的数据一个一个移出去,对应串口协议的数据位。

如,在某时刻向 TDR 写入了 0x55 数据,则其在寄存器中就是二进制存储 01010101,此时硬件检测到输入数据就会检查移位寄存器是否有数据正在移位。如果没有,这个 01010101 就会立刻移动到发送移位寄存器。当数据从 TDR 移动到移位寄存器的时候会置一个标志位叫做 TXE(TX Empty),表示发送寄存器空。如果该位置 1,则可以在 TDR 中继续写入下一个数据位。当 TXE 为 1 时,其实其没有被发送出去,只是移动到了发送移位寄存器。发送移位寄存器会在发生器控制的驱动下,往右移位,将数据输出到 Tx 引脚。向右移位正符合低位先行的协议规范。当移位完成后,新的在 TDR 的数据又会被转移过来。

读取也是类似的。数据从 Rx 引脚通向接收移位寄存器,一位位被读取。先放在最高位,移位 8 次后,就能接收完一个字节。当一个字节接收(移位)完成后,其会被整体的转移到 RDR 中。在转移过程中,会置一个标志位 RXNE(Rx Not Empty),表示接收数据寄存器非空。当检测到其置 1 后,就可以把数据读走了。而发送添加帧头帧尾、接收剔除帧头帧尾等硬件会自动完成。

这边是框图中上半部分的操作。

除此之外,框图中还有两个引脚 RTS 和 CTS 用于数据流控制。RTS(Request To Send)为请求发送,是输出脚,是告诉其他节点当前可不可以接收。CTS(Clear To Send)是清除发送,用于接收其他节点的 CTS 信号。当另一个支持流控的串口的 Tx 接到本机 Rx,本机的 RTS 输出一个能不能接收的反馈信号,接到对方的 CTS。当能接收时 RTS 置低,请求对方发送。对方 CTS 收到后就可以发,如果处理速度不够时,就可以把 RTS 置高,对方 CTS 收到后就可以停止发送直到 RTS 重新置低。

框图中右半部分用于进行产生同步的时钟信号,配合发送移位寄存器输出。发送寄存器没移位一次,同步时钟跳变一个周期,告诉对方移位一位数据。通常用于自适应波特率或者兼容其他协议。

该串口还具有唤醒单元(WAKEUP UNIT)。在此不再赘述。

框图中还具有中断控制(USART INTERRUPT CONTROL)。中断申请位就是状态寄存器的各种标志位,比如 TXE 表示发送寄存器空,RXNE 表示接收寄存器非空,这两个是判断发送状态和接收状态的必要标志位,中断输出控制就是配置中断是不是能够通向NVIC。

其还具有波特率发生器,即分频器。APB 时钟通过分频得到发送接收移位的时钟,即 PCLKx(x=1/2)。之后通过除以一个分频系数 USARTDIV,这个系数有小数部分,支持小数点后四位。其在分频之后除以 16 变成发送接收器时钟。如果 TE(Tx Enable)为 1,则为发送器使能,波特率就有效,RE 同理。

串口的输出 TX 比输入 RX 简单很多,输出就定时翻转 TX 引脚高低电平就可以,输入不仅要保证采样频率和波特率一致,还要保证每次输入采样的位置,要正好处于每一位的正中间,只有在每一位的正中间采样,这样高低电平读进来,才是最可靠的。

alt text

上图为 STM32 设计的起始位侦测。当输入电路侦测到一个数据帧的起始位后,就会以波特率的频率,连续采样一帧数据。

为了实现接收功能,其对输入的电路对采样时钟进行了细分。它会以波特率的16倍频率进行采样,也就是在一位时间里,可以进行 16 次采样。最开始,空闲状态高电平,那采样就一直是 1,在某个位置突然采集到一个 0,那么就说明在这两次采样之间,出现了下降沿。如果没有任何噪声,那之后就应该是起始位了,在起始位,会进行连续 16 次采样,没有噪声的话,这 16 次采样,肯定都是 0。以防万一,这个接收电路还会再下降沿之后的第 3 次、5 次、7 次进行一批采样,在第 8 次、9 次、10 次再进行一批采样,且这两批采样都要求每 3 位里面至少有 2 个 0。如果有轻微的噪声,导致 3 位里面只有两个 0,另一个是 1,也算检测到了起始位,但是在状态寄存器里会置一个 NE(Noise Error)噪声标志位,提醒存在噪声。如果 3 位里面只有一个 0,就不算检测到了起始位。这时电路就忽略前面的数据,重新开始捕捉下降沿。同时,第 8、9、10 次采样的位置是起始位的正中间,之后接收数据位时,就都在第 8、9、10 次进行采样,这样就能保证采样位置在位的正中间了。

SPI 外设

SPI(Serial Peripheral Interface),即串行外设接口,是由 Motorola 公司开发的一种通用数据总线。SPI 协议并没有严格规定最大传输速度,最大传输速度取决于芯片厂商的设计需求。其具有四根通信线:

  • SCK:Serial Clock 串行时钟线
  • MOSI:Master Output Slave Input 主机输出从机输入线
  • MISO:Master Input Slave Output 主机输入从机输出线
  • SS:Slave Select 从机选择

SPI 基本结构

SPI 为同步全双工协议,发送接收互不影响。其连接方式如图所示:

alt text

其中左侧为 SPI 主机,主导整个 SPI 从机。右侧为 SPI 从机 1,2,3,为挂载在主机上的设备。对于每一个主机都需要一根 SS 片选线。SS 线为低电平有效。由于 SPI 所有通信都是单端信号,他们的高低电平都是相对于 GND 的电压差,单端信号所有设备都需要共地。SPI 选择从机的方式不需要像 I2C 一样进行寻址,而且对于输出配置推挽输出,有很强的驱动能力,这使得 SPI 引脚的下降沿、上升沿均也非常迅速,从而能达到更高的传输速度。

alt text

上图为 SPI 的官方手册框图。可以看到,其连接 MOSI 和 MISO 的是同一个 Shift Register,即移位寄存器。MISO 的数据会输出到移位寄存器,然后移位寄存器的数据会输出到 MOSI 引脚。SPI 的数据传输实际上是数据交换。因为 SPI 一般都是高位先行的,所以每来一个时钟,移位寄存器都会向左进行移位。移位寄存器中的时钟源是由主机提供的,叫做波特率发生器(Baud rate generator),它产生的时钟驱动主机的移位寄存器进行移位,同时这个时钟也通过 SCK 引脚进行输出,接到从机的移位寄存器里。主机移位寄存器左边移出去的数据通过 MOSI 引脚输入到从机移位寄存器的右边,从机移位寄存器左边移出去的数据通过 MISO 引脚输入到主机移位寄存器的右边,组成一个圈。规定波特率发生器时钟的上升沿所有移位寄存器向左移位一位,移出去的位放到引脚上,波特率发生器的下降沿引脚上的位,采样输入到移位寄存器的最低位。和 Usart 相似,对于发送接收寄存器和移位寄存器同样有 TXE 和 RXNE 的标志。

SPI 模式

如上图,SPI 外设内部具有 CPOL 和 CPHA 寄存器,用于配置不同的模式。

CPOL 表示空闲状态时 SCK 的高低电平状态,0 为低,1 为高。

CPHA 表示在 SCK 的哪个边沿移入移出数据。0 表示在 SCK 的第一个边沿移入数据,第二个边沿移出数据;1 表示在 SCK 的第一个边沿移出数据,第二个边沿移入数据。

模式 0

CPOL = 0 && CPHA = 0

但是由于数据需要先移出再移入,所以在模式 0 下,SCK 在第一个边沿开始之前就要提前开始移出数据,即在第 0 个边沿移出,第 1 个边沿移入。所以,当 SS 下降沿时就要立即开始移位输出,此时把 SS 下降沿当做时钟的一部分。

模式 1

CPOL = 0 && CPHA = 1

模式 2

CPOL = 1 && CPHA = 0

模式 3

CPOL = 1 && CPHA = 1

SPI 通信

SPI 通常采用的指令码加读写数据的通信模型。

当 SPI 起始时,第一个交换发送给从机的数据一般叫做指令码,在从机中对应的会定义一个指令集。需要发送什么指令,就在起始后第一个字节发送指令集里面的数据。这样就能指导从机完成相应的功能了。下图为 W25Q64 手册中使用 SPI 通信的读取方式。

alt text

比如,可以先交换 05h 给从机,在下一次交换时,主机就可以得到一个字节的 Register-1 寄存器值。

参考文档

https://blog.csdn.net/Johnor/article/details/130895974

https://blog.csdn.net/qq_41844618/article/details/104332949

https://www.cnblogs.com/asandstar/p/16942910.html

https://blog.csdn.net/qq_34686440/article/details/116225349

https://zhuanlan.zhihu.com/p/660212315

https://www.qaqme.cn/stm32-nvic-exti-interrupt-hal/

https://doc.embedfire.com/mcu/stm32/f103mini/std/zh/latest/book/EXTI.html

https://heptari.uk/STM32 系列/ADC 外设 HAL 库配置与编程.html

Last modified: 2026-05-24